package eval.vm;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.inject.Singleton;
import eval.exceptions.VmNotFoundException;
import eval.exceptions.VmAlreadyExistException;
import eval.utilities.configuration.ExperimentConfiguration;
import eval.utilities.random.RandomUtilities;

/**
 * This class manage the VM distribution , adding and removing VM's from cloud.
 * 
 * @author Amit Peled   <amit.kapina@gmail.com>
 * @author Igal Kaufman <igal.kaufman@gmail.com>
 */
@Singleton
public class VMManager implements IVMManager{

	private static ExperimentConfiguration configuration = ExperimentConfiguration
			.getSingleton();

	// maps vm configuration enum to the object containing its configuration
	private Map<VMConfigurationEnum, VMConfiguration> vmConfigurations = new HashMap<VMConfigurationEnum, VMConfiguration>();

	// maps the vm configuration enum to the probability that a vm of this type
	// is in the system
	private Map<VMConfigurationEnum, Double> vmDistribution = new HashMap<VMConfigurationEnum, Double>();

	// list of vms deployed in the system
	private Set<VirtualMachine> knownVMs = new HashSet<VirtualMachine>();

	private RandomUtilities tmp = new RandomUtilities();


	/**
	 * @Constructor
	 */
	public VMManager() {
		loadVMConfigurations();
	}

	/**
	 * Create Data structure
	 * 
	 */
	private void loadVMConfigurations() {
		double probabilitySum = 0;
		for (VMConfigurationEnum value : VMConfigurationEnum.values()) {
			vmConfigurations.put(value, generateVM(value));
			double probability = getVMDistribution(value);
			vmDistribution.put(value, probability);
			probabilitySum += probability;
		}

		if (probabilitySum != 1)
			throw new IllegalArgumentException(
					"The sum of probabilities for VM distribution is not 1");

	}



	/**
	 * get VM Distribution
	 * 
	 * @param value VM type
	 * @return Distribution
	 */
	private Double getVMDistribution(VMConfigurationEnum value) {
		switch (value) {
		case SMALL:
			return new Double(configuration.smallVmProb);
		case MEDIUM:
			return new Double(configuration.mediumVmProb);
		case LARGE:
			return new Double(configuration.largeVmProb);
		case HIGH_CPU:
			return new Double(configuration.highCpuVmProb);
		default:
			throw new IllegalArgumentException("Illegal value for enumeration "
					+ VMConfigurationEnum.class.getSimpleName());
		}
	}

	/**
	 * Create new VM
	 * 
	 * @param value VM type
	 * @return VM configuration
	 */
	private VMConfiguration generateVM(VMConfigurationEnum value) {
		switch (value) {
		case SMALL:
			return new VMConfiguration(configuration.smallVmCpuMin,
					configuration.smallVmCpuMax, configuration.smallVmMemMin,
					configuration.smallVmMemMax);

		case MEDIUM:
			return new VMConfiguration(configuration.mediumVmCpuMin,
					configuration.mediumVmCpuMax, configuration.mediumVmMemMin,
					configuration.mediumVmMemMax);

		case LARGE:
			return new VMConfiguration(configuration.largeVmCpuMin,
					configuration.largeVmCpuMax, configuration.largeVmMemMin,
					configuration.largeVmMemMax);

		case HIGH_CPU:
			return new VMConfiguration(configuration.highCpuVmCpuMin,
					configuration.highCpuVmCpuMax,
					configuration.highCpuVmMemMin,
					configuration.highCpuVmMemMax);

		default:
			throw new IllegalArgumentException("Illegal value for enumeration "
					+ VMConfigurationEnum.class.getSimpleName());
		}
	}


	/* (non-Javadoc)
	 * @see eval.vm.IVMManager#getNumberOfVMs()
	 */
	@Override
	public int getNumberOfVMs() {
		return knownVMs.size();
	}

	/* (non-Javadoc)
	 * @see eval.vm.IVMManager#getVMConfiguration(eval.vm.VMIdentifier)
	 */
	@Override
	public VMConfiguration getVMConfiguration(VMIdentifier vmIdentifier) {
		for (VirtualMachine vm : knownVMs) {
			if (vm.getIdentifier().equals(vmIdentifier)) {
				return vmConfigurations.get(vm.getVMConfigurationEnum());
			}
		}
		throw new IllegalStateException("Looking for a non existing vm");
	}

	/* (non-Javadoc)
	 * @see eval.vm.IVMManager#getVMConfigurationForEnum(eval.vm.VMConfigurationEnum)
	 */
	@Override
	public VMConfiguration getVMConfigurationForEnum(
			VMConfigurationEnum configurationEnum) {
		return vmConfigurations.get(configurationEnum);
	}


	/* (non-Javadoc)
	 * @see eval.vm.IVMManager#generateNewVmAccordingToDistribution()
	 */
	@Override
	public VMConfigurationEnum generateNewVmAccordingToDistribution() {

		double randomNumber =  tmp.generateNewRandomNumber();

		double sum = 0;
		for (VMConfigurationEnum value : VMConfigurationEnum.values()) {
			sum += vmDistribution.get(value);
			if (randomNumber <= sum)
				return value;
		}

		throw new IllegalArgumentException("Error while generating a new vm");
	}

	/* (non-Javadoc)
	 * @see eval.vm.IVMManager#addKnownVMs(java.util.List)
	 */
	@Override
	public void addKnownVMs(List<VirtualMachine> vMs) {
		for(VirtualMachine it : vMs)
			addKnownVM(it);
	}

	@Override
	public void addKnownVM(VirtualMachine virtualMachine) {
		if(knownVMs.contains(virtualMachine))
			throw new VmAlreadyExistException(" trying to add existing vm (duplicate obj) ");
		knownVMs.add(virtualMachine);

	}

	/* (non-Javadoc)
	 * @see eval.vm.IVMManager#getAllVirtualMachines()
	 */
	@Override
	public List<VirtualMachine> getAllVirtualMachines() {
		return new ArrayList<VirtualMachine>(knownVMs);
	}

	/* (non-Javadoc)
	 * @see eval.vm.IVMManager#setSatisfiedLoad(eval.vm.VirtualMachine, int, int)
	 */
	@Override
	public void setSatisfiedLoad(VirtualMachine vm, int satisfiedCpu,int satisfiedMem) {
		vm.setSatisfiedCpuDemand(satisfiedCpu);
		vm.setSatisfiedMemDemand(satisfiedMem);
	}

	/* (non-Javadoc)
	 * @see eval.vm.IVMManager#removeListOfVMs(java.util.List)
	 */
	@Override	
	public void removeListOfVMs(List<VirtualMachine> vmsToRemove){
		if(knownVMs.containsAll(vmsToRemove)){
			knownVMs.removeAll(vmsToRemove);
		}
		else{
			throw new VmNotFoundException("\n trying to remove VM which not exsit in knownVMs.\n");
		}

	}

	/* (non-Javadoc)
	 * @see eval.vm.IVMManager#getVMbyID(eval.vm.VMIdentifier)
	 */
	@Override
	public VirtualMachine getVMbyID(VMIdentifier vmID){
		for(VirtualMachine vm : knownVMs)
			if(vm.getIdentifier() == vmID)
				return vm;
		return null;
	}

	/* (non-Javadoc)
	 * @see eval.vm.IVMManager#getVMbyID(int)
	 */
	@Override
	public VMIdentifier getVMbyID(int id) {
		for(VirtualMachine vm: knownVMs)
			if(vm.getIdentifier().getID()==id)
				return vm.getIdentifier();
		return null;
	}

}
